get_cmake_property(ENABLED_LANGUAGES ENABLED_LANGUAGES)
include(CheckCXXCompilerFlag)
include(CheckCCompilerFlag)

function(list_append var)
  foreach(arg ${ARGN})
    list(FIND ${var} ${arg} found)
    if(${found} EQUAL -1)
      list(APPEND ${var} ${arg})
    endif()
  endforeach()
  set(${var} ${${var}} PARENT_SCOPE)
endfunction()

macro(sanitize_flag_cx)
  foreach(arg ${ARGN})
    string(REPLACE "+" "x" var ${arg})
    string(REGEX REPLACE "[^A-Za-z0-9_-]+" "-" var ${var})
    string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${SANITIZE_CX} ${arg}")
    list(FIND ENABLED_LANGUAGES C found)
    if(${found} GREATER -1)
      check_c_compiler_flag(${arg} cx${var})
      if(cx${var})
        list_append(SANITIZE_CX ${arg})
      endif()
    endif()
    list(FIND ENABLED_LANGUAGES CXX found)
    if(${found} GREATER -1)
      check_cxx_compiler_flag(${arg} cx${var})
      if(cx${var})
        list_append(SANITIZE_CX ${arg})
      endif()
    endif()
    set(CMAKE_REQUIRED_FLAGS)
  endforeach()
endmacro()

macro(sanitize_flag_ld)
  foreach(arg ${ARGN})
    string(REPLACE "+" "x" var ${arg})
    string(REGEX REPLACE "[^A-Za-z0-9_-]+" "-" var ${var})
    string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${SANITIZE_CX} ${SANITIZE_LD} ${arg}")
    list(FIND ENABLED_LANGUAGES C found)
    if(${found} GREATER -1)
      check_c_compiler_flag(${arg} ld${var})
      if(ld${var})
        list_append(SANITIZE_LD ${arg})
      endif()
    endif()
    list(FIND ENABLED_LANGUAGES CXX found)
    if(${found} GREATER -1)
      check_cxx_compiler_flag(${arg} ld${var})
      if(ld${var})
        list_append(SANITIZE_LD ${arg})
      endif()
    endif()
    set(CMAKE_REQUIRED_FLAGS)
  endforeach()
endmacro()

if(
  CMAKE_C_COMPILER_ID MATCHES "GNU|[Cc]lang|LLVM" OR
  CMAKE_CXX_COMPILER_ID MATCHES "GNU|[Cc]lang|LLVM"
)
  sanitize_flag_cx(-fsanitize=address)
  sanitize_flag_cx(-fsanitize=leak)
  sanitize_flag_cx(-fsanitize=undefined)
  sanitize_flag_cx(-fsanitize-recover=all)
  sanitize_flag_cx(-fno-omit-frame-pointer)
  if(
    CMAKE_C_COMPILER_ID MATCHES "GNU" OR
    CMAKE_CXX_COMPILER_ID MATCHES "GNU"
  )
    sanitize_flag_ld(-static-libasan)
    sanitize_flag_ld(-static-liblsan)
    sanitize_flag_ld(-static-libubsan)
  else()
    sanitize_flag_ld(-static-libsan)
  endif()
elseif(
  CMAKE_C_COMPILER_ID MATCHES "MSVC" OR
  CMAKE_CXX_COMPILER_ID MATCHES "MSVC"
)
  list_append(SANITIZE_LD /INCREMENTAL:NO)
  sanitize_flag_cx(/fsanitize=address)
endif()

function(target_compile_sanitize)
  set(scope PRIVATE)
  foreach(target ${ARGN})
    if(target MATCHES "^INTERFACE|PUBLIC|PRIVATE")
      set(scope ${target})
    else()
      target_compile_options(${target} ${scope} ${SANITIZE_CX})
    endif()
  endforeach()
endfunction()

function(target_link_sanitize_3_13)
  function(string_append var)
    foreach(arg ${ARGN})
      string(FIND "${${var}}" "${arg}" found)
      if(${found} EQUAL -1)
        string(STRIP "${${var}} ${arg}" ${var})
      endif()
    endforeach()
    set(${var} "${${var}}" PARENT_SCOPE)
  endfunction()
  foreach(target ${ARGN})
    if(TARGET ${target})
      get_property(LINK_FLAGS TARGET ${target} PROPERTY LINK_FLAGS)
      if(MSVC)
        string_append(LINK_FLAGS ${SANITIZE_LD})
      else()
        string_append(LINK_FLAGS ${SANITIZE_CX} ${SANITIZE_LD})
      endif()
      set_property(TARGET ${target} PROPERTY LINK_FLAGS "${LINK_FLAGS}")
    endif()
  endforeach()
endfunction()

function(target_link_sanitize)
  if(CMAKE_VERSION VERSION_LESS 3.13)
    target_link_sanitize_3_13(${ARGN})
    return()
  endif()
  set(scope PRIVATE)
  foreach(target ${ARGN})
    if(target MATCHES "^INTERFACE|PUBLIC|PRIVATE")
      set(scope ${target})
    elseif(MSVC)
      target_link_options(${target} ${scope} ${SANITIZE_LD})
    else()
      target_link_options(${target} ${scope} ${SANITIZE_CX} ${SANITIZE_LD})
    endif()
  endforeach()
endfunction()
